package com.xiyuan.smartutils.filters;

import com.xiyuan.smartutils.StrUtils;

import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * 包过滤器，* 包下任意字符， ** 多级目录下的任意字符，其中* 必须必须包含一个字符
 *
 * @author xiyuan-lgz 2023-10-16 @version v1.0.0 新建与整理
 */
public class PackageFilter implements Filter<Class<?>> {
    private static final char REG_S = '^';
    private static final char DOT_T = '.';
    private static final char ASTERISK = '*';
    private static final String ASTERISK_S = "**.";
    private static final String ASTERISK_PATTERN = "[0-9a-zA-Z_$]+";
    private Set<PkgMatcher> matchers = new LinkedHashSet<PkgMatcher>(2);
    private Set<PkgMatcher> excludes = new LinkedHashSet<PkgMatcher>(2);
    
    private void init(Set<PkgMatcher> matchers, Collection<String> packages) {
        if (packages == null) {return;}
        for (String item : packages) {
            item = StrUtils.trimToNull(item);
            if (item == null) {continue;}
            matchers.add(PkgMatcher.compile(item));
        }
    }
    
    /**
     * 包过滤器，* 包下任意字符， ** 多级目录下的任意字符，其中* 必须必须包含一个字符
     *
     * @param packages 包名列表
     */
    public PackageFilter(Collection<String> packages) {
        if (packages == null) {return;}
        init(matchers, packages);
    }
    
    /**
     * 包过滤器，* 包下任意字符， ** 多级目录下的任意字符，其中* 必须必须包含一个字符
     *
     * @param packages 包名列表
     * @param excludes 不包含的包名
     */
    public PackageFilter(Collection<String> packages, Collection<String> excludes) {
        if (packages != null) {init(this.matchers, packages);}
        if (excludes != null) {init(this.excludes, excludes);}
    }
    
    @Override
    public boolean accept(Class<?> src) {
        if (src == null) {return false;}
        return accept(src.getPackage().getName());
    }
    
    /**
     * 路径
     *
     * @param pkgName the name of the package
     * @return true|false
     */
    public boolean accept(String pkgName) {
        
        // 优先排除
        if (excludes != null && !excludes.isEmpty()) {
            for (PkgMatcher matcher : excludes) {
                if (matcher.matches(pkgName)) {
                    return false;
                }
            }
        }
        
        // 没有包名的情况下
        if (pkgName.isEmpty() && !matchers.isEmpty()) {
            return false;
        }
        
        // 如果什么都不限制的情况下
        if (matchers.isEmpty()) {
            return true;
        }
        
        // 匹配包名
        for (PkgMatcher matcher : matchers) {
            if (matcher.matches(pkgName)) {
                return true;
            }
        }
        return false;
    }
    
    private static class PkgMatcher {
        private Pattern pattern;
        private int hashCode;
        private String raw;
        
        static PkgMatcher compile(String pattern) {
            PkgMatcher matcher = new PkgMatcher();
            StringBuilder sb = new StringBuilder();
            char ch = 0, p = 0;
            int ctx = 1, sc = 0, sc1 = 0;
            for (int i = 0; i < pattern.length(); i++, p = ch) {
                ch = pattern.charAt(i);
                if ('.' == ch || '/' == ch) {
                    if (p != '.') {
                        sb.append(ch);
                    }
                    sc = 0;
                    ctx = 1;
                    sc1 = 0;
                }
                else if ('*' == ch) {
                    sc++;
                    if (ctx == 1 && sc <= 2 || sc == 1) {
                        sc1++;
                        sb.append(ch);
                    }
                }
                else {
                    if (sc1 > 1) {
                        sb.setLength(sb.length() - 1);
                    }
                    sb.append(ch);
                    sc1 = 0;
                    ctx = 0;
                    sc = 0;
                }
            }
            
            // 原型
            if (DOT_T == sb.charAt(0)) {
                sb.delete(0, 1);
            }
            if (sb.indexOf(ASTERISK_S) == 0) {
                sb.insert(0, ".");
            }
            if (REG_S != sb.charAt(0)) {
                sb.insert(0, REG_S);
            }
            
            ch = sb.charAt(sb.length() - 1);
            if (DOT_T == ch || (ASTERISK != ch && p != DOT_T && p != ASTERISK)) {
                if (DOT_T == ch) {
                    sb.setLength(sb.length() - 1);
                }
                sb.append(".**");
            }
            
            matcher.raw = sb.toString();
            if (sb.indexOf("^.**.") == 0) {
                sb.delete(1, 5);
                sb.insert(1, "([0-9a-zA-Z_$]+.){1,}");
            }
            
            String pt = sb.toString()
                    .replaceAll("[./]\\*\\*", "(.\\[0-9a-zA-Z_\\$\\]+){0,}")
                    // .replaceAll("\\.\\*", ".[0-9a-zA-Z_\\$]+")
                    .replaceAll("\\*", "[0-9a-zA-Z_\\$]+")
                    .replaceAll("[./]", "[./]");
            sb.setLength(0);
            sb.append(pt);
            if (pt.endsWith(ASTERISK_PATTERN)) {
                sb.setCharAt(sb.length() - 1, ASTERISK);
            }
            
            pt = sb.toString();
            matcher.hashCode = pt.hashCode();
            matcher.pattern = Pattern.compile(pt);
            return matcher;
        }
        
        @Override
        public String toString() {
            return new StringBuilder("{ raw: ").append(raw)
                    .append(", hashCode: ")
                    .append(hashCode)
                    .append(", pattern: ")
                    .append(pattern.toString())
                    .append("}")
                    .toString();
        }
        
        public boolean matches(String input) {
            
            return pattern.matcher(input).matches();
        }
        
        @Override
        public int hashCode() {
            return super.hashCode();
        }
        
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            PkgMatcher matcher = (PkgMatcher) o;
            return hashCode == matcher.hashCode && Objects.equals(pattern, matcher.pattern);
        }
    }


//    public static void main(String[] args) {
//
//        // TODO **.com.xiyuan.ss*ss.*.*d* 这种 **.开头的会有问题
//        PkgMatcher matcher = PkgMatcher.compile("com.xiyuan.ss*****ss.*.*d");
//        System.out.println(matcher);
//        System.out.println(matcher.matches("com.xiyuan.ssqqqss.xx.xx"));// false
//        System.out.println(matcher.matches("com/xiyuan/ssqqqss/xx/xxd"));// true
//        System.out.println(matcher.matches("com.xiyuan.ssqqqss.xx.xxdd"));// true
//        System.out.println(matcher.matches("ss.ss.com.xiyuan.ssqqqss.xx.xxdd"));// true
//        System.out.println(matcher.matches("com.xiyuan.ssqqqss.xx.xx.dd.dd"));// false
//        System.out.println(matcher.matches("com.xiyuan.ssqqqss.xx.xx.dd.ddqqq"));// false
//        System.out.println(matcher.matches("com.xiyuan.ssqqqss.xx.xx.dd.ddddqqq"));// true
//
//        matcher = PkgMatcher.compile("com.xiyuan.smartutils.client");
//        System.out.println(matcher);
//        String val = "com.xiyuan.smartutils.client";
//        System.out.println(matcher.matches(val));// true
//
//
//        System.out.println(PkgMatcher.compile("com.xiyuan.smartutils.client"));
//        System.out.println(PkgMatcher.compile("com.xiyuan.smartutils.client*"));
//        System.out.println(PkgMatcher.compile("com.xiyuan.smartutils.client."));
//        System.out.println(PkgMatcher.compile("com.xiyuan.smartutils.client.*"));
//        System.out.println(PkgMatcher.compile("com.xiyuan.smartutils.client.**"));
//        System.out.println(PkgMatcher.compile("**.smartutils"));
//
//        System.out.println(val);
//        System.out.println(PkgMatcher.compile("com.xiyuan.smartutils.client").matches(val));
//        System.out.println(PkgMatcher.compile("com.xiyuan.smartutils.client*").matches(val));
//        System.out.println(PkgMatcher.compile("com.xiyuan.smartutils.client.*").matches(val));
//        System.out.println(PkgMatcher.compile("com.xiyuan.smartutils.client.**").matches(val + ".ss.dd.dd"));
//        System.out.println(PkgMatcher.compile("**.smartutils").matches(val));
//        System.out.println(PkgMatcher.compile("com.*.smartutils.client.**").matches(val + ".ss.dd.dd"));
//        System.out.println(PkgMatcher.compile("com.*.smartutils.client.**.66").matches(val + ".ss.dd.dd"));
//        System.out.println(PkgMatcher.compile("com.*.smartutils.client.**.66").matches(val + ".ss.dd.dd66"));
//        System.out.println(PkgMatcher.compile("com.*.smartutils.client.**.66").matches(val + ".ss.dd.dd.66"));
//    }
}